#include "KeywordProcessor.h"
#include "LogUtils.h"

KeywordProcessor::KeywordProcessor() : m_tokenizer(){
    // 构造函数
    // 初始化停用词集合
    generate_stopwords("corpus/stopwords/cn_stopwords.txt", m_chStopWords);
    generate_stopwords("corpus/stopwords/en_stopwords.txt", m_enStopWords);
}

// chDir: 中文语料库
// enDir: 英文语料库
void KeywordProcessor::process(const string& chDir, const string& enDir){
    // 语料库不为空时生成dict.dat和index_dat
    if(chDir != ""){
        LOG_INFO << "[KeyWordProcessor] Generating dict_cn.dat...";
        create_cn_dict(chDir, "corpus/dict_cn.dat");

        LOG_INFO << "[KeyWordProcessor] Generating index_cn.dat...";
        build_cn_index("corpus/dict_cn.dat", "corpus/index_cn.dat");
    }
    
    if(enDir != ""){
        LOG_INFO <<  "[KeyWordProcessor] Generating dict_en.dat...";
        create_en_dict(enDir, "corpus/dict_en.dat");

        LOG_INFO <<  "[KeyWordProcessor] Generating index_en.dat...";
        build_en_index("corpus/dict_en.dat", "corpus/index_en.dat");
    }

    LOG_INFO << "[KeyWordProcessor] Process finished...";
}

void KeywordProcessor::create_cn_dict(const string& dir, const string& outfile){
    //获取中文语料文件名集合
    vector<string> files = DirectoryScanner::scan(dir);
    //词频统计
    map<string,unsigned long> dict;
    //遍历每个文件
    for(auto filename: files){
        ifstream ifs{dir + "/" + filename};
        if(!ifs.is_open()){
            std::cerr << "CN-corpus file can not open!" << endl;
            exit(-1);
        }
        string line;
        //放文件每一行的单词
        vector<string> words;
        while(std::getline(ifs,line)){
            m_tokenizer.Cut(line,words);
            for(auto word:words){
                if(isStopWord(word,m_chStopWords)){
                    continue;
                }
                if (is_all_non_chinese(word)) {
                    continue;
                }
                
                ++dict[word];
            }
        }
        ifs.close();
    }

    //存储词典到文件中
    ostringstream oss;
    for(auto &[word,frequency]: dict){
        oss << word << " " << frequency << endl;
    }

    ofstream ofs{outfile};
    if(!ofs.is_open()){
        std::cerr << "CN-dict outfile can not open!" << endl;
        exit(-1);
    }
    ofs << oss.str();
    ofs.close();
}

void KeywordProcessor::build_cn_index(const string& dict, const string& index){
    //打开词典库
    ifstream ifs_dict{dict};
    if(!ifs_dict.is_open()){
        std::cerr << "[build_cn_index]: open dict_CN failed!" << endl;
        exit(-1);
    }

    string line;
    map<string,set<int>> _index;
    int lineNo=1;
    //以空格作为单词和词频之间的分隔符
    while(std::getline(ifs_dict,line)){
        istringstream iss{line};
        string word;
        //得到单词
        std::getline(iss,word,' ');
        //将单词分成一个个汉字
        const char* it = word.c_str();
        const char* end = word.c_str() + word.size();

        while (it != end) {
            auto start = it;
            utf8::next(it, end);    // 将it移动到下一个utf8字符所在的位置
            // 因为一个汉字需要占用多个字节,我们可以用std::string来表示一个汉字
            std::string alpha = std::string{ start, it };
            // std::cout << alpha << "\n";
            _index[alpha].insert(lineNo);
        }

        ++lineNo;
    }
    ifs_dict.close();

    //写入INDEX_CN文件
    ostringstream oss;
    for(auto &[alpha,set_index]: _index){
        oss << alpha;
        for(auto idx: set_index){
            oss << " " << idx;
        }
        oss << endl;
    }
    ofstream ofs{index};
    if(!ofs.is_open()){
        std::cerr << "[build_cn_index]: open index_CN failed!" << endl;
        exit(-1);
    }
    ofs << oss.str();
    ofs.close();
}

void KeywordProcessor::create_en_dict(const string& dir, const string& outfile){
    // 读取目录下所有的普通文件
    vector<string> file_list;
    file_list = DirectoryScanner::scan(dir);
    if(file_list.size() == 0){
        // 文件为空直接返回
        return;
    }

    // 用于存储每个单词的频次, 读取完所有文件再写入dict
    map<string, int> word_frequency;

    // 要输出的dict文件
    ofstream dict{ outfile };
    if(!dict.is_open()){
        cerr << "Error: Failed to open dict file." << endl;
        return;
    }

    string line; // 每行文件的内容
    for(auto &filename : file_list){
        // 打开其中一个文件
        string file_path = dir + "/" + filename;
        ifstream file { file_path };
        
        // 判断文件是否存在
        if(!file.is_open()){
        cerr << "Error: Failed to open file." << endl;
            continue;
        }

        while(std::getline(file, line)){
            // 每次读取一行的内容
            vector<string> jieba_result;
            m_tokenizer.Cut(line, jieba_result);
            
            // 对数据进行清洗, 去除数字和标点符号, 将字母统一转换成小写
            clean_and_normalize(jieba_result);

            // 读取一行中的每个字符
            for(auto &w : jieba_result){
                if(w.size() != 0 && m_enStopWords.find(w) == m_enStopWords.end()){
                    // 不是停用词, 词频+1
                    word_frequency[w]++;
                }
            }
        }
    }

    // 读取完成, 将word_frequency数据写入dict
    for(auto const& [word, freq] : word_frequency){
        // 获取的是pair对象
        dict << word << " " << freq << endl;
    }
}

void KeywordProcessor::build_en_index(const string& dict, const string& index){
    // 用于存储每个单词中, 每个字母出现的行数
    map<string, set<int>> char_index;

    // 打开dict_en.dat文件
    ifstream file { dict };

    // 要输出的index文件
    ofstream index_file{ index };
    if(!index_file.is_open()){
        cerr << "Error: Failed to open index file." << endl;
        return;
    }

    string line; // 一行的内容
    stringstream ss;
    string word; // 一行中的第一个单词
    size_t line_count = 0;

    while(std::getline(file, line)){
        // 每次读取一行的内容
        line_count++;
        ss.clear();
        ss.str(line);

        if(ss >> word){
            // 获取一行的第一个单词, 不获取后面的词频
            for(char &c : word){
                // 只构造一次，然后复用
                string key(1, c);
                char_index[key].insert(line_count);
            }
        }
    }

    // 读取完成, 将char_index数据写入index
    for(auto const& [c, line_set] : char_index){
        // 获取的是pair对象
        index_file << c;

        for(auto & line_num : line_set){
            index_file << " " << line_num;
        }

        index_file << endl;
    }
}


